﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.Scripting.Ast;
using Microsoft.Scripting.Runtime;

namespace Cjc.Calculation.ExpressionEngine
{
    public class ScopeAndLanguage
    {
        public LanguageContext LanguageContext { get; set; }
        public WorksheetScope Scope { get; set; }
    }

    public class DlrParameterResolver : ExpressionVisitor
	{
		private static DlrParameterResolver resolver = new DlrParameterResolver();

		public static readonly ParameterExpression ScopeAndLanguageParameter = Expression.Parameter( typeof( ScopeAndLanguage ), "$scopelang" );

		/// <summary>
		/// Visits the children of the extension expression.
		/// </summary>
		/// <param name="node">The expression to visit.</param>
		/// <returns>
		/// The modified expression, if it or any subexpression was modified;
		/// otherwise, returns the original expression.
		/// </returns>
		/// <remarks>
		/// This can be overridden to visit or rewrite specific extension nodes.
		/// If it is not overridden, this method will call <see cref="M:System.Linq.Expressions.Expression.VisitChildren(System.Linq.Expressions.ExpressionVisitor)"/>,
		/// which gives the node a chance to walk its children. By default,
		/// <see cref="M:System.Linq.Expressions.Expression.VisitChildren(System.Linq.Expressions.ExpressionVisitor)"/> will try to reduce the node.
		/// </remarks>
        //protected override Expression VisitExtension( Expression node )
        //{
        //    var context = node as CodeContextExpression;

        //    if ( context != null ) return ContextParameter;

        //    return base.VisitExtension( node );
        //}

		/// <summary>
		/// Visits the <see cref="T:System.Linq.Expressions.ParameterExpression"/>.
		/// </summary>
		/// <param name="node">The expression to visit.</param>
		/// <returns>
		/// The modified expression, if it or any subexpression was modified;
		/// otherwise, returns the original expression.
		/// </returns>
		protected override Expression VisitParameter( ParameterExpression node )
		{
            if (node.Type == typeof(ScopeAndLanguage)) return ScopeAndLanguageParameter;

			return base.VisitParameter( node );
		}

		/// <summary>
		/// Makes the lambda builder.
		/// </summary>
		/// <param name="body">The body.</param>
		/// <param name="name">The name.</param>
		/// <param name="parameters">The parameters.</param>
		/// <returns></returns>
		private static LambdaBuilder MakeLambdaBuilder( Expression body, string name, params ParameterExpression[] parameters )
		{
			var builder = Utils.Lambda( typeof( object ), name );

            builder.AddParameters(ScopeAndLanguageParameter);
			if ( parameters != null ) builder.AddParameters( parameters );

			var visited = resolver.Visit( body );

			builder.Body = ( visited.Type != typeof( object ) )
				? Expression.Convert( visited, typeof( object ) )
				: visited;

			return builder;
		}

		/// <summary>
		/// Makes the lambda.
		/// </summary>
		/// <param name="body">The body.</param>
		/// <param name="parameters">The parameters.</param>
		/// <returns></returns>
		public static LambdaExpression MakeLambda( Expression body, string name, params ParameterExpression[] parameters )
		{
			return MakeLambdaBuilder( body, name, parameters ).MakeLambda();
		}

		/// <summary>
		/// Makes the lambda.
		/// </summary>
		/// <param name="body">The body.</param>
		/// <param name="parameters">The parameters.</param>
		/// <returns></returns>
		public static Expression<TDelegate> MakeLambda<TDelegate>( Expression body, string name, params ParameterExpression[] parameters )
		{
			return (Expression<TDelegate>)MakeLambdaBuilder( body, name, parameters ).MakeLambda( typeof( TDelegate ) );
		}
	}
}